1 /*
2  * Copyright (C) 2002-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 
20 #include "ui_fsmenu/options.h"
21 
22 #include <memory>
23 
24 #include <boost/algorithm/string.hpp>
25 
26 #include "base/i18n.h"
27 #include "base/log.h"
28 #include "base/wexception.h"
29 #include "graphic/default_resolution.h"
30 #include "graphic/font_handler.h"
31 #include "graphic/graphic.h"
32 #include "graphic/mouse_cursor.h"
33 #include "graphic/text/bidi.h"
34 #include "graphic/text/font_set.h"
35 #include "graphic/text_layout.h"
36 #include "io/filesystem/disk_filesystem.h"
37 #include "io/filesystem/layered_filesystem.h"
38 #include "logic/filesystem_constants.h"
39 #include "scripting/lua_interface.h"
40 #include "scripting/lua_table.h"
41 #include "sound/sound_handler.h"
42 #include "wlapplication.h"
43 #include "wlapplication_options.h"
44 
45 namespace {
46 
47 // Locale identifiers can look like this: ca_ES@valencia.UTF-8
48 // The contents of 'selected_locale' will be changed to match the 'current_locale'
find_selected_locale(std::string * selected_locale,const std::string & current_locale)49 void find_selected_locale(std::string* selected_locale, const std::string& current_locale) {
50 	if (selected_locale->empty()) {
51 		std::vector<std::string> parts;
52 		boost::split(parts, current_locale, boost::is_any_of("."));
53 		if (current_locale == parts[0]) {
54 			*selected_locale = current_locale;
55 		} else {
56 			boost::split(parts, parts[0], boost::is_any_of("@"));
57 			if (current_locale == parts[0]) {
58 				*selected_locale = current_locale;
59 			} else {
60 				boost::split(parts, parts[0], boost::is_any_of("_"));
61 				if (current_locale == parts[0]) {
62 					*selected_locale = current_locale;
63 				}
64 			}
65 		}
66 	}
67 }
68 
69 }  // namespace
70 
FullscreenMenuOptions(OptionsCtrl::OptionsStruct opt)71 FullscreenMenuOptions::FullscreenMenuOptions(OptionsCtrl::OptionsStruct opt)
72    : FullscreenMenuBase(),
73 
74      // Values for alignment and size
75      padding_(10),
76 
77      // Title
78      title_(this,
79             0,
80             0,
81             0,
82             0,
83             _("Options"),
84             UI::Align::kCenter,
85             g_gr->styles().font_style(UI::FontStyle::kFsMenuTitle)),
86 
87      // Buttons
88      button_box_(this, 0, 0, UI::Box::Horizontal),
89      cancel_(&button_box_, "cancel", 0, 0, 0, 0, UI::ButtonStyle::kFsMenuSecondary, _("Cancel")),
90      apply_(&button_box_, "apply", 0, 0, 0, 0, UI::ButtonStyle::kFsMenuSecondary, _("Apply")),
91      ok_(&button_box_, "ok", 0, 0, 0, 0, UI::ButtonStyle::kFsMenuPrimary, _("OK")),
92 
93      // Tabs
94      tabs_(this, UI::TabPanelStyle::kFsMenu),
95 
96      box_interface_(&tabs_, 0, 0, UI::Box::Horizontal, 0, 0, padding_),
97      box_interface_left_(&box_interface_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
98      box_windows_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
99      box_sound_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
100      box_saving_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
101      box_game_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
102 
103      // Interface options
104      language_dropdown_(&box_interface_left_,
105                         "dropdown_language",
106                         0,
107                         0,
108                         100,  // 100 is arbitrary, will be resized in layout().
109                         50,
110                         24,
111                         _("Language"),
112                         UI::DropdownType::kTextual,
113                         UI::PanelStyle::kFsMenu,
114                         UI::ButtonStyle::kFsMenuMenu),
115      resolution_dropdown_(&box_interface_left_,
116                           "dropdown_resolution",
117                           0,
118                           0,
119                           100,  // 100 is arbitrary, will be resized in layout().
120                           50,
121                           24,
122                           _("Window Size"),
123                           UI::DropdownType::kTextual,
124                           UI::PanelStyle::kFsMenu,
125                           UI::ButtonStyle::kFsMenuMenu),
126 
127      fullscreen_(&box_interface_left_, Vector2i::zero(), _("Fullscreen"), "", 0),
128      inputgrab_(&box_interface_left_, Vector2i::zero(), _("Grab Input"), "", 0),
129      sdl_cursor_(&box_interface_left_, Vector2i::zero(), _("Use system mouse cursor"), "", 0),
130      sb_maxfps_(&box_interface_left_,
131                 0,
132                 0,
133                 0,
134                 0,
135                 opt.maxfps,
136                 0,
137                 99,
138                 UI::PanelStyle::kFsMenu,
139                 _("Maximum FPS:")),
140      translation_info_(&box_interface_, 0, 0, 100, 100, UI::PanelStyle::kFsMenu),
141 
142      // Windows options
143      snap_win_overlap_only_(
144         &box_windows_, Vector2i::zero(), _("Snap windows only when overlapping"), "", 0),
145      dock_windows_to_edges_(&box_windows_, Vector2i::zero(), _("Dock windows to edges"), "", 0),
146      animate_map_panning_(
147         &box_windows_, Vector2i::zero(), _("Animate automatic map movements"), "", 0),
148 
149      sb_dis_panel_(&box_windows_,
150                    0,
151                    0,
152                    0,
153                    0,
154                    opt.panel_snap_distance,
155                    0,
156                    99,
157                    UI::PanelStyle::kFsMenu,
158                    _("Distance for windows to snap to other panels:"),
159                    UI::SpinBox::Units::kPixels),
160 
161      sb_dis_border_(&box_windows_,
162                     0,
163                     0,
164                     0,
165                     0,
166                     opt.border_snap_distance,
167                     0,
168                     99,
169                     UI::PanelStyle::kFsMenu,
170                     _("Distance for windows to snap to borders:"),
171                     UI::SpinBox::Units::kPixels),
172 
173      // Sound options
174      sound_options_(box_sound_, UI::SliderStyle::kFsMenu),
175 
176      // Saving options
177      sb_autosave_(&box_saving_,
178                   0,
179                   0,
180                   0,
181                   0,
182                   opt.autosave / 60,
183                   0,
184                   100,
185                   UI::PanelStyle::kFsMenu,
186                   _("Save game automatically every:"),
187                   UI::SpinBox::Units::kMinutes,
188                   UI::SpinBox::Type::kBig),
189 
190      sb_rolling_autosave_(&box_saving_,
191                           0,
192                           0,
193                           0,
194                           0,
195                           opt.rolling_autosave,
196                           1,
197                           20,
198                           UI::PanelStyle::kFsMenu,
199                           _("Maximum number of autosave files:"),
200                           UI::SpinBox::Units::kNone,
201                           UI::SpinBox::Type::kBig),
202 
203      zip_(&box_saving_,
204           Vector2i::zero(),
205           _("Compress widelands data files (maps, replays and savegames)"),
206           "",
207           0),
208      write_syncstreams_(&box_saving_,
209                         Vector2i::zero(),
210                         _("Write syncstreams in network games to debug desyncs"),
211                         "",
212                         0),
213 
214      // Game options
215      auto_roadbuild_mode_(
216         &box_game_, Vector2i::zero(), _("Start building road after placing a flag")),
217      transparent_chat_(
218         &box_game_, Vector2i::zero(), _("Show in-game chat with transparent background"), "", 0),
219 
220      /** TRANSLATORS: A watchwindow is a window where you keep watching an object or a map region,*/
221      /** TRANSLATORS: and it also lets you jump to it on the map. */
222      single_watchwin_(&box_game_, Vector2i::zero(), _("Use single watchwindow mode")),
223      /** TRANSLATORS: This refers to to zooming with the scrollwheel.*/
224      ctrl_zoom_(&box_game_, Vector2i::zero(), _("Zoom only when Ctrl is pressed")),
225      game_clock_(&box_game_, Vector2i::zero(), _("Display game time in the top left corner")),
226      os_(opt) {
227 
228 	// Buttons
229 	button_box_.add(UI::g_fh->fontset()->is_rtl() ? &ok_ : &cancel_);
230 	button_box_.add_inf_space();
231 	button_box_.add(&apply_);
232 	button_box_.add_inf_space();
233 	button_box_.add(UI::g_fh->fontset()->is_rtl() ? &cancel_ : &ok_);
234 
235 	// Tabs
236 	tabs_.add("options_interface", _("Interface"), &box_interface_, "");
237 	tabs_.add("options_windows", _("Windows"), &box_windows_, "");
238 	tabs_.add("options_sound", _("Sound"), &box_sound_, "");
239 	tabs_.add("options_saving", _("Saving"), &box_saving_, "");
240 	tabs_.add("options_game", _("Game"), &box_game_, "");
241 
242 	// We want the last active tab when "Apply" was clicked.
243 	if (os_.active_tab < tabs_.tabs().size()) {
244 		tabs_.activate(os_.active_tab);
245 	}
246 
247 	// Interface
248 	box_interface_.add(&box_interface_left_);
249 	box_interface_.add(&translation_info_, UI::Box::Resizing::kExpandBoth);
250 	box_interface_left_.add(&language_dropdown_);
251 	box_interface_left_.add(&resolution_dropdown_);
252 	box_interface_left_.add(&fullscreen_);
253 	box_interface_left_.add(&inputgrab_);
254 	box_interface_left_.add(&sdl_cursor_);
255 	box_interface_left_.add(&sb_maxfps_);
256 
257 	// Windows
258 	box_windows_.add(&snap_win_overlap_only_);
259 	box_windows_.add(&dock_windows_to_edges_);
260 	box_windows_.add(&animate_map_panning_);
261 	box_windows_.add(&sb_dis_panel_);
262 	box_windows_.add(&sb_dis_border_);
263 
264 	// Sound
265 	box_sound_.add(&sound_options_);
266 
267 	// Saving
268 	box_saving_.add(&sb_autosave_);
269 	box_saving_.add(&sb_rolling_autosave_);
270 	box_saving_.add(&zip_);
271 	box_saving_.add(&write_syncstreams_);
272 
273 	// Game
274 	box_game_.add(&auto_roadbuild_mode_);
275 	box_game_.add(&transparent_chat_);
276 	box_game_.add(&single_watchwin_);
277 	box_game_.add(&ctrl_zoom_);
278 	box_game_.add(&game_clock_);
279 
280 	// Bind actions
281 	language_dropdown_.selected.connect([this]() { update_language_stats(false); });
282 	cancel_.sigclicked.connect([this]() { clicked_cancel(); });
283 	apply_.sigclicked.connect([this]() { clicked_apply(); });
284 	ok_.sigclicked.connect([this]() { clicked_ok(); });
285 
286 	/** TRANSLATORS: Options: Save game automatically every: */
287 	sb_autosave_.add_replacement(0, _("Off"));
288 
289 	// Fill in data
290 	// Interface options
291 	for (int modes = 0; modes < SDL_GetNumDisplayModes(0); ++modes) {
292 		SDL_DisplayMode mode;
293 		SDL_GetDisplayMode(0, modes, &mode);
294 		if (800 <= mode.w && 600 <= mode.h &&
295 		    (SDL_BITSPERPIXEL(mode.format) == 32 || SDL_BITSPERPIXEL(mode.format) == 24)) {
296 			ScreenResolution this_res = {
297 			   mode.w, mode.h, static_cast<int32_t>(SDL_BITSPERPIXEL(mode.format))};
298 			if (this_res.depth == 24) {
299 				this_res.depth = 32;
300 			}
301 			if (resolutions_.empty() || this_res.xres != resolutions_.rbegin()->xres ||
302 			    this_res.yres != resolutions_.rbegin()->yres) {
303 				resolutions_.push_back(this_res);
304 			}
305 		}
306 	}
307 
308 	bool did_select_a_res = false;
309 	for (uint32_t i = 0; i < resolutions_.size(); ++i) {
310 		const bool selected = resolutions_[i].xres == opt.xres && resolutions_[i].yres == opt.yres;
311 		did_select_a_res |= selected;
312 		resolution_dropdown_.add(
313 		   /** TRANSLATORS: Screen resolution, e.g. 800 x 600*/
314 		   (boost::format(_("%1% x %2%")) % resolutions_[i].xres % resolutions_[i].yres).str(), i,
315 		   nullptr, selected);
316 	}
317 	if (!did_select_a_res) {
318 		uint32_t entry = resolutions_.size();
319 		resolutions_.resize(entry + 1);
320 		resolutions_[entry].xres = opt.xres;
321 		resolutions_[entry].yres = opt.yres;
322 		resolution_dropdown_.add(
323 		   (boost::format(_("%1% x %2%")) % opt.xres % opt.yres).str(), entry, nullptr, true);
324 	}
325 
326 	fullscreen_.set_state(opt.fullscreen);
327 	inputgrab_.set_state(opt.inputgrab);
328 	sdl_cursor_.set_state(opt.sdl_cursor);
329 
330 	// Windows options
331 	snap_win_overlap_only_.set_state(opt.snap_win_overlap_only);
332 	dock_windows_to_edges_.set_state(opt.dock_windows_to_edges);
333 	animate_map_panning_.set_state(opt.animate_map_panning);
334 
335 	// Saving options
336 	zip_.set_state(opt.zip);
337 	write_syncstreams_.set_state(opt.write_syncstreams);
338 
339 	// Game options
340 	auto_roadbuild_mode_.set_state(opt.auto_roadbuild_mode);
341 	transparent_chat_.set_state(opt.transparent_chat);
342 	single_watchwin_.set_state(opt.single_watchwin);
343 	ctrl_zoom_.set_state(opt.ctrl_zoom);
344 	game_clock_.set_state(opt.game_clock);
345 
346 	// Language options
347 	add_languages_to_list(opt.language);
348 	update_language_stats(true);
349 	layout();
350 }
351 
layout()352 void FullscreenMenuOptions::layout() {
353 
354 	// Values for alignment and size
355 	butw_ = get_w() / 5;
356 	buth_ = get_h() * 9 / 200;
357 	hmargin_ = get_w() * 19 / 200;
358 	int tab_panel_width = get_inner_w() - 2 * hmargin_;
359 	tab_panel_y_ = get_h() * 14 / 100;
360 
361 	// Title
362 	title_.set_size(get_w(), title_.get_h());
363 	title_.set_pos(Vector2i(0, buth_));
364 
365 	// Buttons
366 	cancel_.set_desired_size(butw_, buth_);
367 	apply_.set_desired_size(butw_, buth_);
368 	ok_.set_desired_size(butw_, buth_);
369 	button_box_.set_pos(Vector2i(hmargin_ + butw_ / 3, get_inner_h() - hmargin_));
370 	button_box_.set_size(tab_panel_width - 2 * butw_ / 3, buth_);
371 
372 	// Tabs
373 	tabs_.set_pos(Vector2i(hmargin_, tab_panel_y_));
374 	tabs_.set_size(tab_panel_width, get_inner_h() - tab_panel_y_ - buth_ - hmargin_);
375 
376 	tab_panel_width -= padding_;
377 	const int column_width = tab_panel_width / 2;
378 
379 	// Interface
380 	box_interface_left_.set_desired_size(column_width + padding_, tabs_.get_inner_h());
381 	box_interface_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h());
382 	language_dropdown_.set_desired_size(column_width, language_dropdown_.get_h());
383 	language_dropdown_.set_height(tabs_.get_h() - language_dropdown_.get_y() - buth_ - 3 * padding_);
384 	resolution_dropdown_.set_desired_size(column_width, resolution_dropdown_.get_h());
385 	resolution_dropdown_.set_height(tabs_.get_h() - resolution_dropdown_.get_y() - buth_ -
386 	                                3 * padding_);
387 
388 	fullscreen_.set_desired_size(column_width, fullscreen_.get_h());
389 	inputgrab_.set_desired_size(column_width, inputgrab_.get_h());
390 	sdl_cursor_.set_desired_size(column_width, sdl_cursor_.get_h());
391 	sb_maxfps_.set_unit_width(column_width / 2);
392 	sb_maxfps_.set_desired_size(column_width, sb_maxfps_.get_h());
393 
394 	// Windows options
395 	snap_win_overlap_only_.set_desired_size(tab_panel_width, snap_win_overlap_only_.get_h());
396 	dock_windows_to_edges_.set_desired_size(tab_panel_width, dock_windows_to_edges_.get_h());
397 	animate_map_panning_.set_desired_size(tab_panel_width, animate_map_panning_.get_h());
398 	sb_dis_panel_.set_unit_width(200);
399 	sb_dis_panel_.set_desired_size(tab_panel_width, sb_dis_panel_.get_h());
400 	sb_dis_border_.set_unit_width(200);
401 	sb_dis_border_.set_desired_size(tab_panel_width, sb_dis_border_.get_h());
402 
403 	// Sound options
404 	sound_options_.set_desired_size(tab_panel_width, tabs_.get_inner_h());
405 
406 	// Saving options
407 	sb_autosave_.set_unit_width(250);
408 	sb_autosave_.set_desired_size(tab_panel_width, sb_autosave_.get_h());
409 	sb_rolling_autosave_.set_unit_width(250);
410 	sb_rolling_autosave_.set_desired_size(tab_panel_width, sb_rolling_autosave_.get_h());
411 	zip_.set_desired_size(tab_panel_width, zip_.get_h());
412 	write_syncstreams_.set_desired_size(tab_panel_width, write_syncstreams_.get_h());
413 
414 	// Game options
415 	auto_roadbuild_mode_.set_desired_size(tab_panel_width, auto_roadbuild_mode_.get_h());
416 	transparent_chat_.set_desired_size(tab_panel_width, transparent_chat_.get_h());
417 	single_watchwin_.set_desired_size(tab_panel_width, single_watchwin_.get_h());
418 	ctrl_zoom_.set_desired_size(tab_panel_width, ctrl_zoom_.get_h());
419 	game_clock_.set_desired_size(tab_panel_width, game_clock_.get_h());
420 }
421 
add_languages_to_list(const std::string & current_locale)422 void FullscreenMenuOptions::add_languages_to_list(const std::string& current_locale) {
423 
424 	// We want these two entries on top - the most likely user's choice and the default.
425 	language_dropdown_.add(_("Try system language"), "", nullptr, current_locale == "");
426 	language_dropdown_.add("English", "en", nullptr, current_locale == "en");
427 
428 	// Handle non-standard setups where the locale directory might be missing
429 	if (!g_fs->is_directory(i18n::get_localedir())) {
430 		return;
431 	}
432 
433 	// Add translation directories to the list. Using the LanguageEntries' sortnames as a key for
434 	// getting a sorted result.
435 	std::map<std::string, LanguageEntry> entries;
436 	std::string selected_locale;
437 
438 	try {  // Begin read locales table
439 		LuaInterface lua;
440 		std::unique_ptr<LuaTable> all_locales(lua.run_script("i18n/locales.lua"));
441 		all_locales->do_not_warn_about_unaccessed_keys();  // We are only reading partial information
442 		                                                   // as needed
443 
444 		// We start with the locale directory so we can pick up locales
445 		// that don't have a configuration file yet.
446 		std::unique_ptr<FileSystem> fs(&FileSystem::create(i18n::get_localedir()));
447 		FilenameSet files = fs->list_directory(".");
448 
449 		for (const std::string& localename : files) {  // Begin scan locales directory
450 			const char* path = localename.c_str();
451 			if (!strcmp(FileSystem::fs_filename(path), ".") ||
452 			    !strcmp(FileSystem::fs_filename(path), "..") || !fs->is_directory(path)) {
453 				continue;
454 			}
455 
456 			try {  // Begin read locale from table
457 				std::unique_ptr<LuaTable> table = all_locales->get_table(localename);
458 				table->do_not_warn_about_unaccessed_keys();
459 
460 				std::string name = i18n::make_ligatures(table->get_string("name").c_str());
461 				const std::string sortname = table->get_string("sort_name");
462 				std::unique_ptr<LanguageEntry> entry(new LanguageEntry(localename, name));
463 				entries.insert(std::make_pair(sortname, *entry));
464 				language_entries_.insert(std::make_pair(localename, *entry));
465 
466 				if (localename == current_locale) {
467 					selected_locale = current_locale;
468 				}
469 
470 			} catch (const WException&) {
471 				log("Could not read locale for: %s\n", localename.c_str());
472 				entries.insert(std::make_pair(localename, LanguageEntry(localename, localename)));
473 			}  // End read locale from table
474 		}     // End scan locales directory
475 	} catch (const LuaError& err) {
476 		log("Could not read locales information from file: %s\n", err.what());
477 		return;  // Nothing more can be done now.
478 	}           // End read locales table
479 
480 	find_selected_locale(&selected_locale, current_locale);
481 	for (const auto& entry : entries) {
482 		const LanguageEntry& language_entry = entry.second;
483 		language_dropdown_.add(language_entry.descname.c_str(), language_entry.localename, nullptr,
484 		                       language_entry.localename == selected_locale, "");
485 	}
486 }
487 
488 /**
489  * Updates the language statistics message according to the currently selected locale.
490  * @param include_system_lang We only want to include the system lang if it matches the Widelands
491  * locale.
492  */
update_language_stats(bool include_system_lang)493 void FullscreenMenuOptions::update_language_stats(bool include_system_lang) {
494 	int percent = 100;
495 	std::string message = "";
496 	if (language_dropdown_.has_selection()) {
497 		std::string locale = language_dropdown_.get_selected();
498 		// Empty locale means try system locale
499 		if (locale.empty() && include_system_lang) {
500 			std::vector<std::string> parts;
501 			boost::split(parts, i18n::get_locale(), boost::is_any_of("."));
502 			if (language_entries_.count(parts[0]) == 1) {
503 				locale = parts[0];
504 			} else {
505 				boost::split(parts, parts[0], boost::is_any_of("@"));
506 				if (language_entries_.count(parts[0]) == 1) {
507 					locale = parts[0];
508 				} else {
509 					boost::split(parts, parts[0], boost::is_any_of("_"));
510 					if (language_entries_.count(parts[0]) == 1) {
511 						locale = parts[0];
512 					}
513 				}
514 			}
515 		}
516 
517 		// If we have the locale, grab the stats and set the message
518 		if (language_entries_.count(locale) == 1) {
519 			try {
520 				const LanguageEntry& entry = language_entries_[locale];
521 				Profile prof("i18n/translation_stats.conf");
522 				Section& s = prof.get_safe_section("global");
523 				const int total = s.get_int("total");
524 				s = prof.get_safe_section(locale);
525 				percent = static_cast<int>(floor(100 * s.get_int("translated") / total));
526 				if (percent == 100) {
527 					message =
528 					   /** TRANSLATORS: %s = language name */
529 					   (boost::format(_("The translation into %s is complete.")) % entry.descname).str();
530 				} else {
531 					/** TRANSLATORS: %1% = language name, %2% = percentage */
532 					message = (boost::format(_("The translation into %1% is %2%%% complete.")) %
533 					           entry.descname % percent)
534 					             .str();
535 				}
536 			} catch (...) {
537 			}
538 		}
539 	}
540 
541 	// We will want some help with incomplete translations. We set this lower than 100%,
542 	// because some translators let things drop a bit sometimes because they're busy and
543 	// will catch up with the work later.
544 	if (percent <= 90) {
545 		message = message + " " +
546 		          (boost::format(_("If you wish to help us translate, please visit %s")) %
547 		           "<font underline=1>widelands.org/wiki/TranslatingWidelands</font>")
548 		             .str();
549 	}
550 	// Make font a bit smaller so the link will fit at 800x600 resolution.
551 	translation_info_.set_text(
552 	   as_richtext_paragraph(message, UI::FontStyle::kFsMenuTranslationInfo));
553 }
554 
clicked_apply()555 void FullscreenMenuOptions::clicked_apply() {
556 	end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kApplyOptions);
557 }
558 
clicked_cancel()559 void FullscreenMenuOptions::clicked_cancel() {
560 	g_sh->load_config();
561 	clicked_back();
562 }
563 
get_values()564 OptionsCtrl::OptionsStruct FullscreenMenuOptions::get_values() {
565 	// Write all data from UI elements
566 	// Interface options
567 	if (language_dropdown_.has_selection()) {
568 		os_.language = language_dropdown_.get_selected();
569 	}
570 	if (resolution_dropdown_.has_selection()) {
571 		const uint32_t res_index = resolution_dropdown_.get_selected();
572 		os_.xres = resolutions_[res_index].xres;
573 		os_.yres = resolutions_[res_index].yres;
574 	}
575 	os_.fullscreen = fullscreen_.get_state();
576 	os_.inputgrab = inputgrab_.get_state();
577 	os_.sdl_cursor = sdl_cursor_.get_state();
578 	os_.maxfps = sb_maxfps_.get_value();
579 
580 	// Windows options
581 	os_.snap_win_overlap_only = snap_win_overlap_only_.get_state();
582 	os_.dock_windows_to_edges = dock_windows_to_edges_.get_state();
583 	os_.animate_map_panning = animate_map_panning_.get_state();
584 	os_.panel_snap_distance = sb_dis_panel_.get_value();
585 	os_.border_snap_distance = sb_dis_border_.get_value();
586 
587 	// Saving options
588 	os_.autosave = sb_autosave_.get_value();
589 	os_.rolling_autosave = sb_rolling_autosave_.get_value();
590 	os_.zip = zip_.get_state();
591 	os_.write_syncstreams = write_syncstreams_.get_state();
592 
593 	// Game options
594 	os_.auto_roadbuild_mode = auto_roadbuild_mode_.get_state();
595 	os_.transparent_chat = transparent_chat_.get_state();
596 	os_.single_watchwin = single_watchwin_.get_state();
597 	os_.ctrl_zoom = ctrl_zoom_.get_state();
598 	os_.game_clock = game_clock_.get_state();
599 
600 	// Last tab for reloading the options menu
601 	os_.active_tab = tabs_.active();
602 	return os_;
603 }
604 
605 /**
606  * Handles communication between window class and options
607  */
OptionsCtrl(Section & s)608 OptionsCtrl::OptionsCtrl(Section& s)
609    : opt_section_(s),
610      opt_dialog_(
611         std::unique_ptr<FullscreenMenuOptions>(new FullscreenMenuOptions(options_struct(0)))) {
612 	handle_menu();
613 }
614 
handle_menu()615 void OptionsCtrl::handle_menu() {
616 	FullscreenMenuBase::MenuTarget i = opt_dialog_->run<FullscreenMenuBase::MenuTarget>();
617 	if (i != FullscreenMenuBase::MenuTarget::kBack) {
618 		save_options();
619 	}
620 	if (i == FullscreenMenuBase::MenuTarget::kApplyOptions) {
621 		uint32_t active_tab = opt_dialog_->get_values().active_tab;
622 		g_gr->change_resolution(opt_dialog_->get_values().xres, opt_dialog_->get_values().yres);
623 		g_gr->set_fullscreen(opt_dialog_->get_values().fullscreen);
624 		opt_dialog_.reset(new FullscreenMenuOptions(options_struct(active_tab)));
625 		handle_menu();  // Restart general options menu
626 	}
627 }
628 
options_struct(uint32_t active_tab)629 OptionsCtrl::OptionsStruct OptionsCtrl::options_struct(uint32_t active_tab) {
630 	OptionsStruct opt;
631 	// Interface options
632 	opt.xres = opt_section_.get_int("xres", DEFAULT_RESOLUTION_W);
633 	opt.yres = opt_section_.get_int("yres", DEFAULT_RESOLUTION_H);
634 	opt.fullscreen = opt_section_.get_bool("fullscreen", false);
635 	opt.inputgrab = opt_section_.get_bool("inputgrab", false);
636 	opt.maxfps = opt_section_.get_int("maxfps", 25);
637 	opt.sdl_cursor = opt_section_.get_bool("sdl_cursor", true);
638 
639 	// Windows options
640 	opt.snap_win_overlap_only = opt_section_.get_bool("snap_windows_only_when_overlapping", false);
641 	opt.dock_windows_to_edges = opt_section_.get_bool("dock_windows_to_edges", false);
642 	opt.animate_map_panning = opt_section_.get_bool("animate_map_panning", true);
643 	opt.panel_snap_distance = opt_section_.get_int("panel_snap_distance", 0);
644 	opt.border_snap_distance = opt_section_.get_int("border_snap_distance", 0);
645 
646 	// Saving options
647 	opt.autosave = opt_section_.get_int("autosave", kDefaultAutosaveInterval * 60);
648 	opt.rolling_autosave = opt_section_.get_int("rolling_autosave", 5);
649 	opt.zip = !opt_section_.get_bool("nozip", false);
650 	opt.write_syncstreams = opt_section_.get_bool("write_syncstreams", true);
651 
652 	// Game options
653 	opt.auto_roadbuild_mode = opt_section_.get_bool("auto_roadbuild_mode", true);
654 	opt.transparent_chat = opt_section_.get_bool("transparent_chat", true);
655 	opt.single_watchwin = opt_section_.get_bool("single_watchwin", false);
656 	opt.ctrl_zoom = opt_section_.get_bool("ctrl_zoom", false);
657 	opt.game_clock = opt_section_.get_bool("game_clock", true);
658 
659 	// Language options
660 	opt.language = opt_section_.get_string("language", "");
661 
662 	// Last tab for reloading the options menu
663 	opt.active_tab = active_tab;
664 	return opt;
665 }
666 
save_options()667 void OptionsCtrl::save_options() {
668 	OptionsCtrl::OptionsStruct opt = opt_dialog_->get_values();
669 
670 	// Interface options
671 	opt_section_.set_int("xres", opt.xres);
672 	opt_section_.set_int("yres", opt.yres);
673 	opt_section_.set_bool("fullscreen", opt.fullscreen);
674 	opt_section_.set_bool("inputgrab", opt.inputgrab);
675 	opt_section_.set_int("maxfps", opt.maxfps);
676 	opt_section_.set_bool("sdl_cursor", opt.sdl_cursor);
677 
678 	// Windows options
679 	opt_section_.set_bool("snap_windows_only_when_overlapping", opt.snap_win_overlap_only);
680 	opt_section_.set_bool("dock_windows_to_edges", opt.dock_windows_to_edges);
681 	opt_section_.set_bool("animate_map_panning", opt.animate_map_panning);
682 	opt_section_.set_int("panel_snap_distance", opt.panel_snap_distance);
683 	opt_section_.set_int("border_snap_distance", opt.border_snap_distance);
684 
685 	// Saving options
686 	opt_section_.set_int("autosave", opt.autosave * 60);
687 	opt_section_.set_int("rolling_autosave", opt.rolling_autosave);
688 	opt_section_.set_bool("nozip", !opt.zip);
689 	opt_section_.set_bool("write_syncstreams", opt.write_syncstreams);
690 
691 	// Game options
692 	opt_section_.set_bool("auto_roadbuild_mode", opt.auto_roadbuild_mode);
693 	opt_section_.set_bool("transparent_chat", opt.transparent_chat);
694 	opt_section_.set_bool("single_watchwin", opt.single_watchwin);
695 	opt_section_.set_bool("ctrl_zoom", opt.ctrl_zoom);
696 	opt_section_.set_bool("game_clock", opt.game_clock);
697 
698 	// Language options
699 	opt_section_.set_string("language", opt.language);
700 
701 	WLApplication::get()->set_input_grab(opt.inputgrab);
702 	g_mouse_cursor->set_use_sdl(opt_dialog_->get_values().sdl_cursor);
703 	i18n::set_locale(opt.language);
704 	UI::g_fh->reinitialize_fontset(i18n::get_locale());
705 
706 	// Sound options
707 	g_sh->save_config();
708 
709 	// Now write to file
710 	write_config();
711 }
712